﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
//using triangleRT_CLI;
//using CLI_interface;

namespace Gemren
{
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void PropAnnouncer([MarshalAs(UnmanagedType.LPStr)] string name, int type, [MarshalAs(UnmanagedType.LPStr)] string value);
    public unsafe partial class FormPreview : Form
    {
        [DllImport("gemren.dll")]
        protected static extern void RTannounceProperties(uint scene_handle, [MarshalAs(UnmanagedType.FunctionPtr)] PropAnnouncer announcer);
        [DllImport("gemren.dll")]
        protected static extern uint RTcreateScene([MarshalAs(UnmanagedType.LPStr)] string filename);
        [DllImport("gemren.dll")]
        protected static extern void RTfreeScene(uint hnd);
        [DllImport("gemren.dll")]
        protected static extern void RTsetDim(uint scene_handle, int width, int height);
        [DllImport("gemren.dll")]
        protected static extern void RTsetCam(uint scene_handle, double cx, double cy, double cz, double dx, double dy, double dz);
        [DllImport("gemren.dll")]
        protected static extern int RTshootImage(uint scene_handle, bool fast);
        [DllImport("gemren.dll")]
        protected static extern void RTgetImg(uint scene_handle, uint* img);
        [DllImport("gemren.dll")]
        protected static extern int RTsetInt(uint hnd, [MarshalAs(UnmanagedType.LPStr)] string name, int value);
        [DllImport("gemren.dll")]
        protected static extern int RTsetString(uint hnd, [MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value);
        [DllImport("gemren.dll")]
        protected static extern int RTsetFloat(uint hnd, [MarshalAs(UnmanagedType.LPStr)] string name, float value);
        [DllImport("gemren.dll")]
        protected static extern int RTsetBool(uint hnd, [MarshalAs(UnmanagedType.LPStr)] string name, bool value);
        [DllImport("gemren.dll")]
        protected static extern void RTcameraDirDelta(uint hnd, float dx, float dy, [MarshalAs(UnmanagedType.LPArray)] float[] newdir);
        [DllImport("gemren.dll")]
        protected static extern void RTcameraPosDelta(uint hnd, float dx, float dy, float dz, [MarshalAs(UnmanagedType.LPArray)] float[] newpos);
        [DllImport("gemren.dll")]
        protected static extern void RTgetResolution(uint hnd, [MarshalAs(UnmanagedType.LPArray)] int[] arr);
        [DllImport("gemren.dll")]
        protected static extern int RTsaveImage(uint hnd, [MarshalAs(UnmanagedType.LPStr)] string filename);
        [DllImport("gemren.dll")]
        protected static extern void RTonscreenMaterialName(uint hnd, int x, int y, byte** s);
        uint myscene;
        private Form1 parent;
        int imgWidth=640, imgHeight=480;
        float fps = 1.0f;
        object SppLock = new object();
        bool DrawFast = false; //set to true when moving the camera

        object BackBufferLock = new object();
       object SceneDataLock = new object();
        Bitmap BackBuffer = null;
        
        string scenefile = "";
        System.Diagnostics.Stopwatch sw = null;
        long lasttime = 0;
        int spp = 0;
        int prevspp = 0;
        
        public void SaveImage(string file)
        {

            lock (SceneDataLock)
              //  RTsaveImage(myscene, file);
                  switch (System.IO.Path.GetExtension(file))
            {
                case ".png": pictureBox1.Image.Save(file); break;
                case ".hdr": RTsaveImage(myscene, file); break;
            }

        }
        public bool LoadScene(string file)
        {
            scenefile = file;
            myscene = RTcreateScene(file);
            if (myscene == 0)
            {
                MessageBox.Show("Unable to read selected scene definition.");
               // Close();
                return false;
            }
          //  RTsetDim(myscene, imgWidth, imgHeight);
            return true;
        }
        public void GetProperties(PropAnnouncer announce)
        {
            if (myscene == 0) //bad scene - nothing to do here
                Close();
            else
                RTannounceProperties(myscene, announce);
        }
        public void Toggle()
        {
            if (backgroundWorker1.IsBusy) 
                backgroundWorker1.CancelAsync();
            else 
                StartWorker();
        }
       
        public FormPreview(Form1 par, string scenefile)
        {
            InitializeComponent();
            parent = par;
            if (LoadScene(scenefile))
            {
                int[] res = { 0, 0 };
                RTgetResolution(myscene, res);
                imgWidth = res[0];
                imgHeight = res[1];
                pictureBox1.Width = imgWidth;
                pictureBox1.Height = imgHeight;

                this.Show();
                StartWorker();
            }
        }
       
        public void StartWorker()
        {
            pictureBox1.Height = imgHeight;
            pictureBox1.Width = imgWidth;
            lock(backgroundWorker1) backgroundWorker1.RunWorkerAsync();
            
        }
        private unsafe void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {//render
            System.Threading.Thread.CurrentThread.Priority = System.Threading.ThreadPriority.BelowNormal;
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); //for move speed
            while (true)
            {
                try
                {
                    sw.Restart();
                    Bitmap b;
                    System.Drawing.Imaging.BitmapData imdata;
                    lock (SceneDataLock)
                    {
                        if (backgroundWorker1.CancellationPending) break;
                        b = new Bitmap(imgWidth, imgHeight);
                        imdata = b.LockBits(
                                new Rectangle(0, 0, imgWidth, imgHeight),
                                System.Drawing.Imaging.ImageLockMode.WriteOnly,
                                System.Drawing.Imaging.PixelFormat.Format32bppRgb
                            );
                        int dx = NewMouseX - OldMouseX,
                            dy = NewMouseY - OldMouseY;
                        if (dx != 0 || dy != 0)
                        {
                            float[] nd = new float[3];
                            RTcameraDirDelta(myscene, (dx) * -0.003f, (dy) * 0.003f, nd);
                            parent.SetCameraDir(nd[0], nd[1], nd[2]);
                            OldMouseX = NewMouseX;
                            OldMouseY = NewMouseY;
                        }
                        if (PressedButtons != 0)
                        {
                            float[] np = new float[3];
                            float mx = 0, my = 0, mz = 0;
                            if ((PressedButtons & GetKeyBit('w')) != 0) mz += 1.0f;
                            if ((PressedButtons & GetKeyBit('s')) != 0) mz += -1.0f;
                            if ((PressedButtons & GetKeyBit('a')) != 0) mx += -1.0f;
                            if ((PressedButtons & GetKeyBit('d')) != 0) mx += 1.0f;
                            if ((PressedButtons & GetKeyBit('e')) != 0) my += 1.0f;
                            if ((PressedButtons & GetKeyBit('q')) != 0) my += -1.0f;
                            float factor =  Math.Max(sw.ElapsedMilliseconds,50)/ 1000.0f;
                            mx *= factor;
                            my *= factor;
                            mz *= factor;

                            RTcameraPosDelta(myscene, mx, my, mz, np);
                            parent.SetCameraPos(np[0], np[1], np[2]);
                            PressedButtons = 0;
                        }
                        int nspp = RTshootImage(myscene, DrawFast);
                        lock (SppLock)
                        {
                            spp = nspp;
                        }
                        RTgetImg(myscene, (uint*)imdata.Scan0);
                    }
                    // imdata.Scan0 = (IntPtr)d;
                    b.UnlockBits(imdata);
                    lock (BackBufferLock)
                        BackBuffer = b;

                    backgroundWorker1.ReportProgress(0);
                }
                catch
                {
                        
                }
            }
              
            
            
        }
        
        private void FormPreview_Activated(object sender, EventArgs e)
        {
            parent.FocusWindow(this);
        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        { //called upon completion of every 1spp image
            if(sw == null){
                sw  = new System.Diagnostics.Stopwatch();
                sw.Start();
            }
            Bitmap b;
            lock(BackBufferLock)
            {
                b = pictureBox1.Image as Bitmap;
                try
                {
                    pictureBox1.Image = BackBuffer;//  ++progressBar1.Value;
                }
                catch(Exception ex) { 
                    MessageBox.Show(ex.Message); 
                }
            }
          
            long time = sw.ElapsedMilliseconds;
            long dtime = time - lasttime;
            fps = 1000.0f / dtime;
            lock (SppLock)
            {
                if (spp < prevspp) //image was reset
                    sw.Restart();
                prevspp = spp;
            }
            this.Text = (sw.ElapsedMilliseconds/1000).ToString() + "s, \t " + prevspp.ToString() + "spp,\t" + fps.ToString() + "fps";
            
            lasttime = sw.ElapsedMilliseconds;
        }

      
        public unsafe void SetExposition(double ex)
        {
            lock(SceneDataLock) RTsetFloat(myscene, "exposition", (float)ex);
        }
        public void SetCam(double cx, double cy, double cz, double dx, double dy, double dz)
        {
            lock (SceneDataLock)
            {
                RTsetCam(myscene, cx, cy, cz, dx, dy, dz);
            }
        }

        
        private void FormPreview_FormClosed(object sender, FormClosedEventArgs e)
        {
            parent.Deselect();
            backgroundWorker1.CancelAsync();
            lock (SceneDataLock)
                RTfreeScene(myscene);
        }

        string[] errors = { "ok", "Error: Bad object name.", "Error: Bad property name.", "Error: Bad value format." };
        public void SetInt(string name, int value)
        {
            int ret = 0;
            lock (SceneDataLock)
            {
                ret = RTsetInt(myscene, name, value);
            }
            if (ret != 0) MessageBox.Show(errors[ret]);
            
        }
        public void SetString(string name, string value)
        {
            int ret = 0;
            lock (SceneDataLock)
            {
                ret = RTsetString(myscene, name, value);
                int[] res = { 0, 0 };
                RTgetResolution(myscene, res);
                if (res[0] != imgWidth || res[1] != imgHeight)
                {
                   // backgroundWorker1.CancelAsync();
                    imgWidth = res[0];
                    imgHeight = res[1];
                    pictureBox1.Width = imgWidth;
                    pictureBox1.Height = imgHeight;
                }

            }
            if (ret != 0) MessageBox.Show(errors[ret]);

        }
        public void SetFloat(string name, float value)
        {
            int ret = 0;
            lock (SceneDataLock)
            {
                ret = RTsetFloat(myscene, name, value);
            }
            if (ret != 0) MessageBox.Show(errors[ret]);
        }
        public void SetBool(string name, bool value)
        {
            int ret = 0;
            lock (SceneDataLock)
            {
                ret = RTsetBool(myscene, name, value);
            }
            if (ret != 0) MessageBox.Show(errors[ret]);
        }

        private void FormPreview_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (backgroundWorker1.IsBusy) backgroundWorker1.CancelAsync();
        }

        int OldMouseX, OldMouseY, NewMouseX, NewMouseY;
        bool IsDown = false;
        private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
        {
            if (IsDown && e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                NewMouseX = e.X;
                NewMouseY = e.Y;
                //IsDirChanged = true;
            }
        }

        private void pictureBox1_Click(object sender, EventArgs e)
        {
            
        }

        private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
        {
            IsDown = true; //against the after loading yank
            OldMouseX = NewMouseX = e.X;
            OldMouseY = NewMouseY = e.Y;
            if(e.Button == System.Windows.Forms.MouseButtons.Left) 
                DrawFast = true; //begin panning the camera
        }
        uint PressedButtons = 0;
        uint GetKeyBit(char key)
        {
            switch (key)
            {
                case 'w': return 1U;
                case 's': return 2U;
                case 'a': return 4U;
                case 'd': return 8U;
                case 'e': return 16U;
                case 'q': return 32U;
            }
            return 0;
        }
        
        private void FormPreview_KeyPress(object sender, KeyPressEventArgs e)
        {
            float fps = Math.Min(60.0f, this.fps);
            lock (SceneDataLock)
            {
                PressedButtons |= GetKeyBit(e.KeyChar);
            }
        }


        private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button == System.Windows.Forms.MouseButtons.Right)
            {
                fixed(byte** cp = new byte*[1])
                {
                    RTonscreenMaterialName(myscene, e.X, e.Y, cp);
                    StringBuilder sb = new StringBuilder();
                    byte* c = *cp;
                    while (*c != '\0')
                        sb.Append((char)*(c++));
                    parent.SelectMaterial(sb.ToString());
                }
            }
            DrawFast = false;
        }

        private void FormPreview_KeyDown(object sender, KeyEventArgs e)
        {
            DrawFast = true;
        }

        private void FormPreview_KeyUp(object sender, KeyEventArgs e)
        {
            DrawFast = false;
        }

        private void FormPreview_Leave(object sender, EventArgs e)
        {
            DrawFast = false;
        }

        private void FormPreview_Deactivate(object sender, EventArgs e)
        {
            DrawFast = false;
        }

    }
}
